package org.yaxim.androidclient.data;

import java.util.ArrayList;

import org.yaxim.androidclient.BuildConfig;
import org.yaxim.androidclient.util.LogConstants;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;

public class ChatProvider extends ContentProvider {

	public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".provider.Chats";
	public static final String TABLE_NAME = "chats";
	public static final String TABLE_ARCHIVE = "archive";
	public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
			+ "/" + TABLE_NAME);
	public static final Uri ARCHIVE_URI = Uri.parse("content://" + AUTHORITY
			+ "/" + TABLE_ARCHIVE);

	private static final UriMatcher URI_MATCHER = new UriMatcher(
			UriMatcher.NO_MATCH);

	private static final int MESSAGES = 1;
	private static final int MESSAGE_ID = 2;
	private static final int ARCHIVE = 3;

	static {
		URI_MATCHER.addURI(AUTHORITY, "chats", MESSAGES);
		URI_MATCHER.addURI(AUTHORITY, "chats/#", MESSAGE_ID);
		URI_MATCHER.addURI(AUTHORITY, "archive", ARCHIVE);
	}

	private static final String TAG = "yaxim.ChatProvider";

	private SQLiteOpenHelper mOpenHelper;

	public ChatProvider() {
	}

	@Override
	public int delete(Uri url, String where, String[] whereArgs) {
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();
		int count;
		switch (URI_MATCHER.match(url)) {

		case MESSAGES:
			count = db.delete(TABLE_NAME, where, whereArgs);
			break;
		case MESSAGE_ID:
			String segment = url.getPathSegments().get(1);

			if (TextUtils.isEmpty(where)) {
				where = "_id=" + segment;
			} else {
				where = "_id=" + segment + " AND (" + where + ")";
			}

			count = db.delete(TABLE_NAME, where, whereArgs);
			break;
		default:
			throw new IllegalArgumentException("Cannot delete from URL: " + url);
		}

		getContext().getContentResolver().notifyChange(url, null);
		return count;
	}

	@Override
	public String getType(Uri url) {
		int match = URI_MATCHER.match(url);
		switch (match) {
		case MESSAGES:
			return ChatConstants.CONTENT_TYPE;
		case MESSAGE_ID:
			return ChatConstants.CONTENT_ITEM_TYPE;
		default:
			throw new IllegalArgumentException("Unknown URL");
		}
	}

	@Override
	public Uri insert(Uri url, ContentValues initialValues) {
		switch (URI_MATCHER.match(url)) {
		case ARCHIVE:
			SQLiteDatabase db = mOpenHelper.getWritableDatabase();
			db.insert(TABLE_ARCHIVE, null, initialValues);
			return null;
		case MESSAGES:
			break; // fall through
		default:
			throw new IllegalArgumentException("Cannot insert into URL: " + url);
		}

		ContentValues values = (initialValues != null) ? new ContentValues(
				initialValues) : new ContentValues();

		for (String colName : ChatConstants.getRequiredColumns()) {
			if (values.containsKey(colName) == false) {
				throw new IllegalArgumentException("Missing column: " + colName);
			}
		}

		SQLiteDatabase db = mOpenHelper.getWritableDatabase();

		long rowId = db.insert(TABLE_NAME, ChatConstants.DATE, values);

		if (rowId < 0) {
			throw new SQLException("Failed to insert row into " + url);
		}

		Uri noteUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
		getContext().getContentResolver().notifyChange(noteUri, null);
		return noteUri;
	}

	@Override
	public boolean onCreate() {
		mOpenHelper = new ChatDatabaseHelper(getContext());
		return true;
	}

	private void appendWhereJidCount(SQLiteQueryBuilder qBuilder, String jid, String count) {
		// optimized query to get last N entries:
		//    SELECT * FROM chats
		//    WHERE jid="?"
		//    AND _id > IFNULL((SELECT _id FROM chats WHERE jid="?" ORDER BY _id DESC LIMIT 1 OFFSET 200), 0)
		qBuilder.appendWhere("jid=");
		qBuilder.appendWhereEscapeString(jid);
		long lCount = Long.valueOf(count);
		if (lCount < 0) {
			// negative count - everything starting with _id == -count
			qBuilder.appendWhere("AND _id >= ");
			qBuilder.appendWhereEscapeString(String.valueOf(-lCount));
		} else {
			qBuilder.appendWhere("AND _id > IFNULL((SELECT _id FROM chats WHERE jid=");
			qBuilder.appendWhereEscapeString(jid);
			qBuilder.appendWhere("ORDER BY _id DESC LIMIT 1 OFFSET ");
			qBuilder.appendWhereEscapeString(count);
			qBuilder.appendWhere("), 0)");
		}
	}

	@Override
	public Cursor query(Uri url, String[] projectionIn, String selection,
			String[] selectionArgs, String sortOrder) {

		SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
		int match = URI_MATCHER.match(url);

		switch (match) {
		case MESSAGES:
			qBuilder.setTables(TABLE_NAME);
			break;
		case MESSAGE_ID:
			qBuilder.setTables(TABLE_NAME);
			qBuilder.appendWhere("_id=");
			qBuilder.appendWhere(url.getPathSegments().get(1));
			break;
		case ARCHIVE:
			qBuilder.setTables(TABLE_ARCHIVE);
			if (TextUtils.isEmpty(sortOrder))
				sortOrder = "jid";
			break;
		default:
			throw new IllegalArgumentException("Unknown URL " + url);
		}

		String orderBy;
		if (TextUtils.isEmpty(sortOrder)) {
			orderBy = ChatConstants.DEFAULT_SORT_ORDER;
		} else {
			orderBy = sortOrder;
		}

		SQLiteDatabase db = mOpenHelper.getReadableDatabase();
		Cursor ret = qBuilder.query(db, projectionIn, selection, selectionArgs,
				null, null, orderBy);

		if (ret == null) {
			infoLog("ChatProvider.query: failed");
		} else {
			ret.setNotificationUri(getContext().getContentResolver(), CONTENT_URI);
		}

		return ret;
	}

	@Override
	public int update(Uri url, ContentValues values, String where,
			String[] whereArgs) {
		int count;
		long rowId = 0;
		int match = URI_MATCHER.match(url);
		SQLiteDatabase db = mOpenHelper.getWritableDatabase();

		switch (match) {
		case MESSAGES:
			count = db.update(TABLE_NAME, values, where, whereArgs);
			break;
		case MESSAGE_ID:
			String segment = url.getPathSegments().get(1);
			rowId = Long.parseLong(segment);
			count = db.update(TABLE_NAME, values, "_id=" + rowId, null);
			break;
		default:
			throw new UnsupportedOperationException("Cannot update URL: " + url);
		}

		infoLog("*** notifyChange() rowId: " + rowId + " url " + url);

		getContext().getContentResolver().notifyChange(url, null);
		return count;

	}

	private static void infoLog(String data) {
		if (LogConstants.LOG_INFO) {
			Log.i(TAG, data);
		}
	}

	private static class ChatDatabaseHelper extends SQLiteOpenHelper {

		private static final String DATABASE_NAME = "yaxim.db";
		private static final int DATABASE_VERSION = 9;

		public ChatDatabaseHelper(Context context) {
			super(context, DATABASE_NAME, null, DATABASE_VERSION);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			if (LogConstants.LOG_DEBUG) {
				infoLog("creating new chat table");
			}

			db.execSQL("CREATE TABLE " + TABLE_NAME + " (" 
					+ ChatConstants._ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"
					+ ChatConstants.DATE + " INTEGER,"
					+ ChatConstants.DIRECTION + " INTEGER,"
					+ ChatConstants.JID + " TEXT,"
					+ ChatConstants.MESSAGE + " TEXT,"
					+ ChatConstants.MSGFLAGS + " INT,"
					+ ChatConstants.EXTRA + " TEXT,"
					+ ChatConstants.ERROR + " TEXT,"
					+ ChatConstants.CORRECTION + " TEXT,"
					+ ChatConstants.DELIVERY_STATUS + " INTEGER,"
					+ ChatConstants.PACKET_ID + " TEXT,"
					+ ChatConstants.UNIQUE_ID + " TEXT,"
					+ ChatConstants.RESOURCE + " TEXT DEFAULT NULL);");

			db.execSQL("CREATE TABLE IF NOT EXISTS archive (" +
					"jid TEXT PRIMARY KEY ON CONFLICT REPLACE," +
					"uid TEXT," +
					"date INTEGER)");
			db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_pid on chats (pid)");
			db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_date on chats (jid, date)");
			db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_uid on chats (jid, uid)");
			db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_from_read on chats (jid, from_me, read)");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			infoLog("onUpgrade: from " + oldVersion + " to " + newVersion);
			switch (oldVersion) {
			case 3:
				db.execSQL("UPDATE " + TABLE_NAME + " SET READ=1");
			case 4:
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.PACKET_ID + " TEXT");
			case 5:
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.RESOURCE + " TEXT DEFAULT NULL");
			case 6:
				// moved INDEX creation to version 8->9, because it wasn't in onCreate()
			case 7:
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.MSGFLAGS + " INT DEFAULT 0");
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.EXTRA + " TEXT DEFAULT NULL");
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.ERROR + " TEXT DEFAULT NULL");
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.CORRECTION + " TEXT DEFAULT NULL");
			case 8:
				db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD " + ChatConstants.UNIQUE_ID + " TEXT DEFAULT NULL");
				db.execSQL("CREATE TABLE IF NOT EXISTS archive (" +
						"jid TEXT PRIMARY KEY ON CONFLICT REPLACE," +
						"uid TEXT," +
						"date INTEGER)");

				db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_pid on chats (pid)");
				db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_date on chats (jid, date)");
				db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_uid on chats (jid, uid)");
				db.execSQL("CREATE INDEX IF NOT EXISTS idx_chat_jid_from_read on chats (jid, from_me, read)");
				break;
			default:
				db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
				onCreate(db);
			}
		}

	}

	public static final class ChatConstants implements BaseColumns {

		private ChatConstants() {
		}

		public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.yaxim.chat";
		public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.yaxim.chat";
		public static final String DEFAULT_SORT_ORDER = "_id ASC"; // sort by auto-id

		public static final String DATE = "date";
		public static final String DIRECTION = "from_me";
		public static final String JID = "jid";
		public static final String MESSAGE = "message";
		public static final String MSGFLAGS = "msgtype";
		public static final String EXTRA = "extra";
		public static final String ERROR = "error";
		public static final String CORRECTION = "correction";
		public static final String DELIVERY_STATUS = "read"; // SQLite can not rename columns, reuse old name
		public static final String PACKET_ID = "pid";
		public static final String UNIQUE_ID = "uid";
		public static final String RESOURCE = "resource"; // to identify senders in MUCs (among others)

		// boolean mappings
		public static final int INCOMING = 0;
		public static final int OUTGOING = 1;
		// delivery status mappings
		public static final int DS_NEW = 0; //< this message has not been sent/displayed yet
		public static final int DS_SENT_OR_READ = 1; //< this message was sent but not yet acked, or it was received and read
		public static final int DS_ACKED = 2; //< this message was XEP-0184 acknowledged
		public static final int DS_FAILED = 3; //< this message was returned as failed

		// message flags mappings
		public static final int MF_TEXT = 0; //< this is a regular chat message
		public static final int MF_FILE = 1; //< this is a file upload URL / SIMS / OOB
		public static final int MT_INVITE = 2; //< this is a chatroom invitation
		public static final int MF_DELAY = 4; //< this message was delayed
		public static final int MF_CORRECT = 8; //< this message was corrected

		public static ArrayList<String> getRequiredColumns() {
			ArrayList<String> tmpList = new ArrayList<String>();
			tmpList.add(DATE);
			tmpList.add(DIRECTION);
			tmpList.add(JID);
			tmpList.add(MESSAGE);
			return tmpList;
		}

	}

}
